package com.jiyici.utils;

import android.annotation.SuppressLint;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;

public class DateGroup {

    public static final int GROUP_INVALID = 0;
    public static final int GROUP_BEYOND_NEXT_MONTH = GROUP_INVALID + 1;
    public static final int GROUP_NEXT_MONTH = GROUP_BEYOND_NEXT_MONTH + 1;
    public static final int GROUP_LATER_THIS_MONTH = GROUP_NEXT_MONTH + 1;
    public static final int GROUP_3_WEEKS_AWAY = GROUP_LATER_THIS_MONTH + 1;
    public static final int GROUP_2_WEEKS_AWAY = GROUP_3_WEEKS_AWAY + 1;
    public static final int GROUP_NEXT_WEEK = GROUP_2_WEEKS_AWAY + 1;
    public static final int GROUP_THIS_WEEK_AFTER_TOMORROW = GROUP_NEXT_WEEK + 1;
    public static final int GROUP_TOMORROW = GROUP_THIS_WEEK_AFTER_TOMORROW + 1;
    public static final int GROUP_TODAY = GROUP_TOMORROW + 1;
    public static final int GROUP_YESTERDAY = GROUP_TODAY + 1;
    public static final int GROUP_THIS_WEEK_BEFORE_YESTERDAY = GROUP_YESTERDAY + 1;
    public static final int GROUP_LAST_WEEK = GROUP_THIS_WEEK_BEFORE_YESTERDAY + 1;
    public static final int GROUP_2_WEEKS_AGO = GROUP_LAST_WEEK + 1;
    public static final int GROUP_3_WEEKS_AGO = GROUP_2_WEEKS_AGO + 1;
    public static final int GROUP_EARLIER_THIS_MONTH = GROUP_3_WEEKS_AGO + 1;
    public static final int GROUP_LAST_MONTH = GROUP_EARLIER_THIS_MONTH + 1;
    public static final int GROUP_OLDER = GROUP_LAST_MONTH + 1;
    
    public static String getGroupName(int group) {
        switch (group) {
        case DateGroup.GROUP_INVALID:
            return "INVALID";
        case DateGroup.GROUP_BEYOND_NEXT_MONTH:
            return "BEYOND_NEXT_MONTH";
        case DateGroup.GROUP_NEXT_MONTH:
            return "NEXT_MONTH";
        case DateGroup.GROUP_LATER_THIS_MONTH:
            return "LATER_THIS_MONTH";
        case DateGroup.GROUP_3_WEEKS_AWAY:
            return "3_WEEKS_AWAY";
        case DateGroup.GROUP_2_WEEKS_AWAY:
            return "2_WEEKS_AWAY";
        case DateGroup.GROUP_NEXT_WEEK:
            return "NEXT_WEEK";
        case DateGroup.GROUP_THIS_WEEK_AFTER_TOMORROW:
            return "THIS_WEEK_AFTER_TOMORROW";
        case DateGroup.GROUP_TOMORROW:
            return "TOMORROW";
        case DateGroup.GROUP_TODAY:
            return "TODAY";
        case DateGroup.GROUP_YESTERDAY:
            return "YESTERDAY";
        case DateGroup.GROUP_THIS_WEEK_BEFORE_YESTERDAY:
            return "THIS_WEEK_BEFORE_YESTERDAY";
        case DateGroup.GROUP_LAST_WEEK:
            return "LAST_WEEK";
        case DateGroup.GROUP_2_WEEKS_AGO:
            return "2_WEEKS_AGO";
        case DateGroup.GROUP_3_WEEKS_AGO:
            return "3_WEEKS_AGO";
        case DateGroup.GROUP_EARLIER_THIS_MONTH:
            return "EARLIER_THIS_MONTH";
        case DateGroup.GROUP_LAST_MONTH:
            return "LAST_MONTH";
        case DateGroup.GROUP_OLDER:
            return "OLDER";

        default:
            return "unknown";
        }
    }
    
    /**
     * Get the date group for a sorted date list (like outlook inbox):
     * 
     * <pre>
     *      Groups:
     *      beyond next month: yyyy/MM/dd 
     *      next month: yyyy/MM/dd
     *      later this month: yyyy/MM/dd
     *      three weeks away: yyyy/MM/dd
     *      two weeks away: yyyy/MM/dd
     *      next week: MM/dd (week)
     *      this week after tomorrow: HH:mm
     *      tomorrow: HH:mm
     *      today: HH:mm
     *      yesterday: HH:mm
     *      this week before yesterday: HH:mm
     *      last week: MM/dd (week)
     *      two weeks ago: yyyy/MM/dd
     *      three weeks ago: yyyy/MM/dd
     *      earlier this month: yyyy/MM/dd
     *      last month: yyyy/MM/dd
     *      older: yyyy/MM/dd
     * </pre>
     * 
     * @param dateList
     *            - the sorted date list (ASC or DESC)
     * @return
     */
    public static int[] getDateGroup(ArrayList<Calendar> dateList) {
        return getDateGroup(getToday(), dateList);
    }
    
    public static int[] getDateGroup(Calendar today, ArrayList<Calendar> dateList) {
        int size = dateList.size();
        int[] groupArray = new int[size];
        for (int i = 0; i < size; i++) {
            groupArray[i] = getGroupOfDate(dateList.get(i), today);
        }
        return groupArray;
    }

    public static int getGroupOfDate(Calendar date, Calendar today) {
        if (isToday(date, today))
            return DateGroup.GROUP_TODAY;
        if (date.after(today)) {
            if (isBeyondNextMonth(date, today))
                return DateGroup.GROUP_BEYOND_NEXT_MONTH;
            if (isTomorrow(date, today))
                return DateGroup.GROUP_TOMORROW;
            if (isSameWeek(date, today))
                return DateGroup.GROUP_THIS_WEEK_AFTER_TOMORROW;
            if (isNextWeek(date, today))
                return DateGroup.GROUP_NEXT_WEEK;
            if (isTwoWeeksAway(date, today))
                return DateGroup.GROUP_2_WEEKS_AWAY;
            if (isThreeWeeksAway(date, today))
                return DateGroup.GROUP_3_WEEKS_AWAY;
            if (isSameMonth(date, today))
                return DateGroup.GROUP_LATER_THIS_MONTH;
            if (isNextMonth(date, today))
                return DateGroup.GROUP_NEXT_MONTH;
        } else {
            if (isOlder(date, today))
                return DateGroup.GROUP_OLDER;
            if (isYesterday(date, today))
                return DateGroup.GROUP_YESTERDAY;
            if (isSameWeek(date, today))
                return DateGroup.GROUP_THIS_WEEK_BEFORE_YESTERDAY;
            if (isLastWeek(date, today))
                return DateGroup.GROUP_LAST_WEEK;
            if (isTwoWeeksAgo(date, today))
                return DateGroup.GROUP_2_WEEKS_AGO;
            if (isThreeWeeksAgo(date, today))
                return DateGroup.GROUP_3_WEEKS_AGO;
            if (isSameMonth(date, today))
                return DateGroup.GROUP_EARLIER_THIS_MONTH;
            if (isLastMonth(date, today))
                return DateGroup.GROUP_LAST_MONTH;
        }
        return DateGroup.GROUP_INVALID;
    }

    public static Calendar getToday() {
        return Calendar.getInstance();
    }

    private static Calendar getDate(Calendar date) {
        Calendar c = Calendar.getInstance();
        c.setTime(date.getTime());
        return c;
    }

    private static Calendar getYesterday(Calendar today) {
        Calendar date = getDate(today);
        date.add(Calendar.DATE, -1);
        return date;
    }

    private static Calendar getTommorow(Calendar today) {
        Calendar date = getDate(today);
        date.add(Calendar.DATE, 1);
        return date;
    }

    private static Calendar getNextNWeek(int weeksAway, Calendar today) {
        Calendar date = getDate(today);
        date.add(Calendar.WEEK_OF_YEAR, weeksAway);
        return date;
    }

    private static Calendar getNextNMonth(int monthsAway, Calendar today) {
        Calendar date = getDate(today);
        date.add(Calendar.MONTH, monthsAway);
        return date;
    }

    public static boolean isSameDay(Calendar date1, Calendar date2) {
        return date1.get(Calendar.YEAR) == date2.get(Calendar.YEAR)
                && date1.get(Calendar.MONTH) == date2.get(Calendar.MONTH)
                && date1.get(Calendar.DATE) == date2.get(Calendar.DATE);
    }

    private static boolean isSameMonth(Calendar date1, Calendar date2) {
        return date1.get(Calendar.YEAR) == date2.get(Calendar.YEAR)
                && date1.get(Calendar.MONTH) == date2.get(Calendar.MONTH);
    }

    private static boolean isSameWeek(Calendar date1, Calendar date2) {
        return date1.get(Calendar.WEEK_OF_YEAR) == date2.get(Calendar.WEEK_OF_YEAR);
    }

    private static boolean isBeyondNextMonth(Calendar date, Calendar today) {
        Calendar nextMonth = getNextNMonth(1, today);
        int nextMonthYear = nextMonth.get(Calendar.YEAR);
        int dateYear = date.get(Calendar.YEAR);
        if (nextMonthYear == dateYear) {
            return date.get(Calendar.MONTH) > nextMonth.get(Calendar.MONTH);
        }
        return dateYear > nextMonthYear;
    }

    private static boolean isNextMonth(Calendar date, Calendar today) {
        return isSameMonth(getNextNMonth(1, today), date);
    }

    private static boolean isThreeWeeksAway(Calendar date, Calendar today) {
        return isSameWeek(date, getNextNWeek(3, today));
    }

    private static boolean isTwoWeeksAway(Calendar date, Calendar today) {
        return isSameWeek(date, getNextNWeek(2, today));
    }

    private static boolean isNextWeek(Calendar date, Calendar today) {
        return isSameWeek(date, getNextNWeek(1, today));
    }

    private static boolean isTomorrow(Calendar date, Calendar today) {
        return isSameDay(getTommorow(today), date);
    }

    private static boolean isToday(Calendar date, Calendar today) {
        return isSameDay(today, date);
    }

    private static boolean isYesterday(Calendar date, Calendar today) {
        return isSameDay(getYesterday(today), date);
    }

    private static boolean isLastWeek(Calendar date, Calendar today) {
        return isSameWeek(date, getNextNWeek(-1, today));
    }

    private static boolean isTwoWeeksAgo(Calendar date, Calendar today) {
        return isSameWeek(date, getNextNWeek(-2, today));
    }

    private static boolean isThreeWeeksAgo(Calendar date, Calendar today) {
        return isSameWeek(date, getNextNWeek(-3, today));
    }

    private static boolean isLastMonth(Calendar date, Calendar today) {
        return isSameMonth(getNextNMonth(-1, today), date);
    }

    private static boolean isOlder(Calendar date, Calendar today) {
        Calendar nextMonth = getNextNMonth(-1, today);
        int nextMonthYear = nextMonth.get(Calendar.YEAR);
        int dateYear = date.get(Calendar.YEAR);
        if (nextMonthYear == dateYear) {
            return date.get(Calendar.MONTH) < nextMonth.get(Calendar.MONTH);
        }
        return dateYear < nextMonthYear;
    }
    
    /************************TEST****************************/
    
    private static void print(Object msg) {
        System.out.println(msg);
    }

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        ArrayList<Calendar> list = getTestDateList();
        Calendar today = buildCalendar("20140211");
        long now = System.currentTimeMillis();
        getDateGroup(today, list);
        print("time: " + (System.currentTimeMillis() - now));
    }

    private static ArrayList<Calendar> getTestDateList() throws Exception {
        ArrayList<Calendar> list = new ArrayList<Calendar>();
        list.add(buildCalendar("20140211"));

        list.add(buildCalendar("20140210"));

        list.add(buildCalendar("20140208"));
        list.add(buildCalendar("20140207"));
        list.add(buildCalendar("20140204"));
        list.add(buildCalendar("20140203"));

        list.add(buildCalendar("20140130"));
        list.add(buildCalendar("20140127"));
        list.add(buildCalendar("20140126"));

        list.add(buildCalendar("20140125"));
        list.add(buildCalendar("20140119"));

        list.add(buildCalendar("20140118"));
        list.add(buildCalendar("20140102"));

        list.add(buildCalendar("20131231"));
        list.add(buildCalendar("20131221"));
        list.add(buildCalendar("20131214"));
        list.add(buildCalendar("20131207"));
        list.add(buildCalendar("20131129"));
        list.add(buildCalendar("20131123"));
        list.add(buildCalendar("20131116"));
        list.add(buildCalendar("20131109"));
        list.add(buildCalendar("20131102"));
        list.add(buildCalendar("20131015"));
        list.add(buildCalendar("20130928"));

        Collections.sort(list, new Comparator<Calendar>() {

            @Override
            public int compare(Calendar o1, Calendar o2) {
                return o1.compareTo(o2);
            }
            
        });
        return list;
    }

    @SuppressLint("SimpleDateFormat")
    private static Calendar buildCalendar(String date) throws Exception {
        DateFormat df = new SimpleDateFormat("yyyyMMdd");
        Date d = df.parse(date);
        Calendar c = Calendar.getInstance();
        c.setTime(d);
        return c;
    }

}
